/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "string.hh"
#include "util.hh"

#include <sys/types.h>
#include <regex.h>
#include <string.h>
#include <ctype.h>

// DEFINES
#define INC_DOC               32768
#define INC_INDEX              4096
#define MAX_MATCH               256

CLASS_INIT(string); // STRING CLASS

string::string()
{
    CLASS_CTOR(string);
    ctor();
}

string::~string()
{
    CLASS_DTOR(string);
    dtor();
}

void string::ctor()
{
    str=NULL;
    n_str=n_str_max=0;
}

void string::dtor()
{
    if(str)
    {
	for(int i=0; i<n_str_max; i++)
	    if(str[i]) free(str[i]);
	str=(char**)realloc(str, 0);
    }
    n_str=n_str_max=0;
}

void string::reserve_memory(int index_to_be_used, int str_size)
{
    if(index_to_be_used>=n_str_max)
    {
	int new_n_str_max=index_to_be_used+16;
	str=(char**)realloc(str, new_n_str_max*sizeof(char*));
	memset(str+n_str_max, 0, (new_n_str_max-n_str_max)*sizeof(char*));
	n_str_max=new_n_str_max;
    }
    if(str_size>=0) str[index_to_be_used]=(char*)realloc(str[index_to_be_used], str_size);
}

char *string::escape(const char *orig)
{
    char *dev=escape_volatile(orig);
    n_str++;
    return dev;
}

char *string::escape_volatile(const char *orig)
{
    int n_orig=strlen(orig);
    reserve_memory(n_str, (n_orig+1)<<1);
    char *dev=str[n_str];
    int n_dev=0;
    for(int i=0; orig[i]; i++)
    {
	char c=orig[i];
	if(c=='\"' || c=='\'') dev[n_dev++]='\\';
	dev[n_dev++]=c;
    }
    dev[n_dev]=0;
    return dev;
}

char *string::format(const char *format, ...)
{
    int size=256;
    while(1)
    {
	reserve_memory(n_str, size);
	va_list ap;
	va_start(ap, format);
	int real_size=vsnprintf(str[n_str], size, format, ap);
	va_end(ap);
	if(real_size<0) return NULL;
	else if(real_size>=size) size<<=1;
	else break;
    }
    n_str++;
    return str[n_str-1];
}

char *string::format_volatile(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    char *result=format_volatile_va(format, ap);
    va_end(ap);
    return result;
}

char *string::format_volatile_va(const char *format, va_list ap)
{
    int size=256;
    while(1)
    {
	reserve_memory(n_str, size);
	int real_size=vsnprintf(str[n_str], size, format, ap);
	if(real_size<0) return NULL;
	else if(real_size>=size) size<<=1;
	else break;
    }
    return str[n_str];
}

int string::get_frame()
{
    return n_str;
}

void string::set_frame(int f)
{
    n_str=f;
}

void string::reset_frame()
{
    n_str=0;
}

void string::test_regex(char *expression, char *str)
{
    regex_t reg;
    regmatch_t match[MAX_MATCH];
    if(regcomp(&reg, expression, REG_EXTENDED)) return;
    if(!regexec(&reg, str, MAX_MATCH, match, 0))
    {
	printf("Result from {%s} in {%s}\n", expression, str);
	int i=0;
	while(match[i].rm_so>=0)
	{
	    printf("%4d %4d ", match[i].rm_so, match[i].rm_eo);
	    for(int j=match[i].rm_so; j<match[i].rm_eo; j++)
		printf("%c", str[j]);
	    printf("\n");
	    i++;
	}
    }
    regfree(&reg);
}

char *string::regex(const char *expression, char *str, int group)
{
    regex_t reg;
    regmatch_t match[MAX_MATCH];
    char *dev=NULL;
    if(regcomp(&reg, expression, REG_EXTENDED)) return NULL;
    if(!regexec(&reg, str, MAX_MATCH, match, 0) && group<MAX_MATCH && match[group].rm_so>=0)
    {
	str[match[group].rm_eo]=0;
	dev=&str[match[group].rm_so];
    }
    regfree(&reg);
    return dev;
}

int string::regex_indexes(const char *expression, char *str, int group, int *len)
{
    regex_t reg;
    regmatch_t match[MAX_MATCH];
    int dev=-1;
    if(regcomp(&reg, expression, REG_EXTENDED)) return dev;
    if(!regexec(&reg, str, MAX_MATCH, match, 0) && group<MAX_MATCH && match[group].rm_so>=0)
    {
	dev=match[group].rm_so;
	if(len) *len=match[group].rm_eo-match[group].rm_so;
    }
    regfree(&reg);
    return dev;
}

bool string::regex_subst(const char *expression, const char *news, char *string, int max_string)
{
    int pos, len;
    if((pos=string::regex_indexes(expression, string, 0/*group*/, &len))>=0)
    {
	if(!news) news="";
	int i_news=strlen(news);
	int i_string=strlen(string);
	if(i_string+i_news-len>=max_string) return false;

	// move trailing part of string
	memmove(&string[pos+i_news], &string[pos+len], i_string-pos-len);
	string[i_string+i_news-len]=0;

	// copy new substring
	memcpy(&string[pos], news, i_news);
	return true;
    }
    return false;
}

char *string::search(char *str, char *needle, char *block_pairs)
{
    // needle
    if(!needle || !*needle) ERROR_R0("no needle");
    int n_needle=strlen(needle);

    // block analysis
    int n_block_pairs=strlen(block_pairs);
    if(n_block_pairs&1) ERROR("block pairs must be even sized string");
    n_block_pairs>>=1;
    int *bp_count=new int[n_block_pairs];
    for(int i=0; i<n_block_pairs; i++) bp_count[i]=0;
    int bp_count_global=0, bp_string=0;

    // search loop
    char *dev=NULL;
    int n_str=strlen(str);
    for(int i=0; i<n_str; i++)
    {
	if(!bp_count_global && !strncmp(needle, &str[i], n_needle)) {dev=&str[i]; break;}
	if(bp_string && str[i]!='\"') continue;
	for(int j=0; j<n_block_pairs<<1; j++)
	{
	    if(block_pairs[j]==str[i])
	    {
		if(!(j&1))
		{
		    if(block_pairs[j+1]==block_pairs[j])
		    {
			// one level block with same open close symbols, like ""
			if(bp_count[j>>1]) bp_count_global--; else bp_count_global++;
			bp_count[j>>1]^=1;
			if(block_pairs[j]=='\"') bp_string^=1;
		    }
		    else
		    {
			bp_count[j>>1]++;
			bp_count_global++;
		    }
		}
		else
		{
		    if(--bp_count[j>>1]<0)
			ERROR("outside %c%c global block in {%s}", block_pairs[j-1], block_pairs[j], str);
		    bp_count_global--;
		}
		break;
	    }
	}
	if(bp_count_global<0) ERROR("outside global block in {%s}", str);
    }
    if(bp_count) delete []bp_count;
    return dev;
}

int string::strcmp_nospc(char *s1, char *s2)
{
    return strncmp_nospc(s1, s2, strlen(s1));
}

int string::strncmp_nospc(char *s1, char *s2, int n1, int *n_string2)
{
    char *l1=NULL, *l2=NULL;
    char *end=s1+n1;
    char *s2_orig=s2;
    while(1)
    {
	while(isspace(*s1)) s1++;
	while(isspace(*s2)) s2++;
	if(s1>=end) break; // equal

	// removable spaces
	int spc1, spc2;
	if(l1 && s1-l1>1 && isalnum(*s1) && isalnum(*l1)) spc1=1; else spc1=0;
	if(l2 && s2-l2>1 && isalnum(*s2) && isalnum(*l2)) spc2=1; else spc2=0;
	if(spc1 ^ spc2) return 1; // one must have has space but the other no -> different

	if(!*s1 && !*s2) break; // equal strings (strings both finished)
	if(*s1 != *s2) return 1; // different strings
	l1=s1;
	l2=s2;
	s1++;
	s2++;
    }
    if(n_string2) *n_string2=s2-s2_orig;
    return 0;
}

int string::strcmp_nospc(char *s1, char *s2, char stop)
{
    int n1=strlen(s1);
    int n2=strlen(s2);
    int m=(n1>n2)?n1:n2;
    char *tmp;
    if((tmp=strchr(s1, stop)))
    {
	int n_tmp=(int)(tmp-s1);
	if(n_tmp<m) m=n_tmp;
    }
    if((tmp=strchr(s2, stop)))
    {
	int n_tmp=(int)(tmp-s2);
	if(n_tmp<m) m=n_tmp;
    }
    return strncmp(s1, s2, m);
}


// STRING_INDEX CLASS

string_index::string_index()
{
    util::ctor_vector(doc, n_doc, max_doc);
    util::ctor_vector(index, n_index, max_index);
}

string_index::~string_index()
{
    util::dtor_vector(doc, n_doc, max_doc);
    util::dtor_vector(index, n_index, max_index);
}

int string_index::search_add_string(char *str)
{
    if(!str) return -1;
    int i_index=search_nearest_index(str);
    if(i_index<n_index && !strcmp(&doc[index[i_index]], str)) return index[i_index];

    // add string
    int n_str=strlen(str);
    int i=n_doc;
    n_doc+=n_str+1;
    if(n_doc>max_doc)
    {
	max_doc+=INC_DOC;
	doc=(char*)realloc(doc, max_doc);
    }
    memcpy(&doc[i], str, n_str+1);

    // add index
    util::inc_vector(index, n_index, max_index, INC_INDEX);
    if(n_index-1-i_index) memmove(&index[i_index+1], &index[i_index], (n_index-1-i_index)*sizeof(int));
    index[i_index]=i;

    return i;
}

int string_index::search_add_string_format(const char *format, ...)
{
    ::string str;

    // call end format
    va_list ap;
    va_start(ap, format);
    char *result=str.format_volatile_va(format, ap);
    va_end(ap);

    return search_add_string(result);
}

int string_index::search_nearest_index(char *str)
{
    int ini=0, end=n_index;
    while(end>ini)
    {
	int mid=(ini+end)>>1;
	if(strcmp(&doc[index[mid]], str)>=0) end=mid; else ini=mid+1;
    }
    return ini;
}

int string_index::search_string(char *str)
{
    int ini=search_nearest_index(str);
    if(ini<n_index && !strcmp(&doc[index[ini]], str)) return index[ini]; else return -1;
}

int string_index::search_prefix_string(char *str)
{
    int ini=search_nearest_index(str);
    if(ini<n_index && !strncmp(&doc[index[ini]], str, strlen(str))) return index[ini]; else return -1;
}

int string_index::search_prefix_string_index(char *str)
{
    int ini=search_nearest_index(str);
    if(ini<n_index && !strncmp(&doc[index[ini]], str, strlen(str))) return ini; else return -1;
}

int string_index::search_string_index(char *str)
{
    int ini=search_nearest_index(str);
    if(ini<n_index && !strcmp(&doc[index[ini]], str)) return ini; else return -1;
}

int string_index::search_string_index_format(const char *format, ...)
{
    ::string str;

    // call end format
    va_list ap;
    va_start(ap, format);
    char *result=str.format_volatile_va(format, ap);
    va_end(ap);

    return search_string_index(result);
}

char *string_index::string(int index)
{
    if(index<0) return NULL;
    return &doc[index];
}

char *string_index::i_string(int index_idx)
{
    if(index_idx<0 || index_idx>=n_index) return NULL;
    return string(index[index_idx]);
}

void string_index::delete_string(int i_index)
{
    if(i_index<0 || i_index>=n_index) return;
    if(n_index-i_index-1) memmove(&index[i_index], &index[i_index+1], (n_index-i_index-1)*sizeof(int));
    n_index--;
}

void string_index::interchange(string_index &d)
{
    char *charp_tmp;
    int *intp_tmp, int_tmp;
    charp_tmp = d.doc;       d.doc       = doc;       doc       = charp_tmp;
    int_tmp   = d.n_doc;     d.n_doc     = n_doc;     n_doc     = int_tmp;
    int_tmp   = d.max_doc;   d.max_doc   = max_doc;   max_doc   = int_tmp;
    intp_tmp  = d.index;     d.index     = index;     index     = intp_tmp;
    int_tmp   = d.n_index;   d.n_index   = n_index;   n_index   = int_tmp;
    int_tmp   = d.max_index; d.max_index = max_index; max_index = int_tmp;
}

void string_index::dump(FILE *arch)
{
    fprintf(arch, "size of doc: %8d\n", n_doc);
    fprintf(arch, "# strings:   %8d\n", n_index);
    for(int i=0; i<n_index; i++)
	fprintf(arch, "\t%8d {%s}\n", i, &doc[index[i]]);
}
